package com.dylan.job.scheduler;


import com.dylan.job.config.JobAdminConfig;
import com.dylan.job.cron.CronExpression;
import com.dylan.job.entity.po.JobInfo;
import com.dylan.job.trigger.JobTriggerPoolHelp;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @Author dylan
 * @Description: 调度器工具类，所以要暴露获取实例方法，私有化构造函数
 * @Create: 2024/10/26 16:20
 */
@Slf4j
public class JobSchedulerHelp {

    private void JobSchedulerHelp() {

    }

    private volatile boolean scheduleThreadToStop = false;

    public static final long PRE_READ_MS = 5000;    // pre read  //查询近5秒的数据

    private static final JobSchedulerHelp INSTANCE = new JobSchedulerHelp();

    private static final Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();

    /**
     * @Author dylan
     * @Description: 提供实例方法
     * @Create: 2024/10/26 16:22
     */
    public static JobSchedulerHelp getInstance() {
        return INSTANCE;
    }

    /**
     * 调度线程
     */
    private Thread schedulerThread;

    private Thread ringThread;

    private boolean ringThreadToStop;

    /**
     * @Author dylan
     * @Description: 创建并启动线程
     * @Create: 2024/10/26 16:24
     */
    public void start() {
        schedulerThread = new Thread(() -> {

            //如果线程没有停止，一直循环获取数据进行处理
            while (!scheduleThreadToStop) {
                try {
                    // 1、pre read
                    long nowTime = System.currentTimeMillis();

                    //预取值，休眠使用参数
                    boolean preReadSuc = true;

                    //1.查询任务 进行处理
                    List<JobInfo> jobList = querayJobList(nowTime + PRE_READ_MS, 50);
                    if (jobList == null || jobList.size() <= 0) {
                        preReadSuc = false;
                    }

                    for (JobInfo jobInfo : jobList) {

                        //如果在当前时间以内，就直接
                        if (nowTime >= jobInfo.getTriggerNextTime()) {
                            //1.交给触发器触发任务执行
                            JobTriggerPoolHelp.trigger(jobInfo.getId());
                        } else {
                            //2.否则还没到触发时间，将数据添加到时间轮等待触发
                            int ringSecond = (int) ((jobInfo.getTriggerNextTime() / 1000) % 60);
                            pushRingData(ringSecond, jobInfo.getId());
                        }
                        // 3、fresh next triggerTime
                        refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
                    }

                    //TODO 更新任务下一次的触发时间
                    for (JobInfo jobInfo : jobList) {
                        JobAdminConfig.getConfig().getJobInfoMapper().updateById(jobInfo);
                    }

                    long cost = System.currentTimeMillis() - nowTime;

                    if (cost < 1000) {  //如果执行时间小于1秒则需要睡眠
                        TimeUnit.MILLISECONDS.sleep((preReadSuc ? 1000 : 5000) - System.currentTimeMillis() % 1000);
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }


            }
        });

        //启动线程
        schedulerThread.setName("schedulerThread");
        schedulerThread.start();

        ringThread = new Thread(() -> {
            while (!ringThreadToStop) {
                //1.时间对齐
                try {
                    TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //2.从时间轮上获取任务进行触发执行 ,为了
                List<Integer> taskList = new ArrayList<>();

                int currentSecond = Calendar.getInstance().get(Calendar.SECOND);
                for (int i = 0; i < 2; i++) {
                    List<Integer> tempList = ringData.get((currentSecond + 60 - i) % 60);
                    if (tempList != null) {
                        taskList.addAll(tempList);
                    }
                }

                //触发任务
                if (taskList.size() > 0) {
                    // do trigger
                    for (int jobId : taskList) {
                        // do trigger
                        JobTriggerPoolHelp.trigger(jobId);
                    }
                    // clear
                    taskList.clear();
                }
            }
        });
        ringThread.setDaemon(true);
        ringThread.setName("ringThread");
        ringThread.start();

    }

    /**
     * @author dylan
     * @description 将数据添加到时间轮
     * @date 2025/2/9  22:19
     */
    private void pushRingData(int ringSecond, Integer jobId) {
        List<Integer> ringDataList = ringData.get(ringSecond);
        if (null == ringDataList) {
            ringDataList = new ArrayList<>();
            ringData.put(ringSecond, ringDataList);
        }
        ringDataList.add(jobId);
    }

    private void refreshNextValidTime(JobInfo jobInfo, Date fromTime) throws Exception {
        Date nextValidTime = generateNextValidTime(jobInfo, fromTime);
        if (nextValidTime != null) {
            jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
            jobInfo.setTriggerNextTime(nextValidTime.getTime());
        } else {
            jobInfo.setTriggerStatus(0);
            jobInfo.setTriggerLastTime(0L);
            jobInfo.setTriggerNextTime(0L);
            log.warn(">>>>>>>>>>> xxl-job, refreshNextValidTime fail for job: jobId={}, scheduleType={}, scheduleConf={}",
                    jobInfo.getId(), jobInfo.getScheduleType(), jobInfo.getScheduleConf());
        }
    }

    // ---------------------- tools ----------------------
    public static Date generateNextValidTime(JobInfo jobInfo, Date fromTime) throws Exception {
        return new CronExpression(jobInfo.getScheduleConf()).getNextValidTimeAfter(fromTime);
    }

    /**
     * @author dylan
     * @description 根据预取查询任务集合
     * @date 2025/1/16  21:22
     */
    private List<JobInfo> querayJobList(long maxNextTriggerTime, int preReadCount) {
        List<JobInfo> jobInfos = JobAdminConfig.getConfig().getJobInfoMapper().scheduleJobQuery(maxNextTriggerTime, preReadCount);
        return jobInfos;
    }

    private List<JobInfo> findAll() {
        return null;
    }
}
